1   /*
2    * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.awt;
27  
28  import java.awt.Font;
29  import java.io.DataInputStream;
30  import java.io.DataOutputStream;
31  import java.io.File;
32  import java.io.FileInputStream;
33  import java.io.InputStream;
34  import java.io.IOException;
35  import java.io.OutputStream;
36  import java.nio.charset.Charset;
37  import java.nio.charset.CharsetEncoder;
38  import java.security.AccessController;
39  import java.security.PrivilegedAction;
40  import java.util.Arrays;
41  import java.util.HashMap;
42  import java.util.HashSet;
43  import java.util.Hashtable;
44  import java.util.Locale;
45  import java.util.Map.Entry;
46  import java.util.Properties;
47  import java.util.Set;
48  import java.util.Vector;
49  import sun.font.CompositeFontDescriptor;
50  import sun.font.SunFontManager;
51  import sun.font.FontManagerFactory;
52  import sun.font.FontUtilities;
53  import sun.util.logging.PlatformLogger;
54  
55  /**
56   * Provides the definitions of the five logical fonts: Serif, SansSerif,
57   * Monospaced, Dialog, and DialogInput. The necessary information
58   * is obtained from fontconfig files.
59   */
60  public abstract class FontConfiguration {
61  
62      //static global runtime env
63      protected static String osVersion;
64      protected static String osName;
65      protected static String encoding; // canonical name of default nio charset
66      protected static Locale startupLocale = null;
67      protected static Hashtable localeMap = null;
68      private static FontConfiguration fontConfig;
69      private static PlatformLogger logger;
70      protected static boolean isProperties = true;
71  
72      protected SunFontManager fontManager;
73      protected boolean preferLocaleFonts;
74      protected boolean preferPropFonts;
75  
76      private File fontConfigFile;
77      private boolean foundOsSpecificFile;
78      private boolean inited;
79      private String javaLib;
80  
81      /* A default FontConfiguration must be created before an alternate
82       * one to ensure proper static initialisation takes place.
83       */
84      public FontConfiguration(SunFontManager fm) {
85          if (FontUtilities.debugFonts()) {
86              FontUtilities.getLogger()
87                  .info("Creating standard Font Configuration");
88          }
89          if (FontUtilities.debugFonts() && logger == null) {
90              logger = PlatformLogger.getLogger("sun.awt.FontConfiguration");
91          }
92          fontManager = fm;
93          setOsNameAndVersion();  /* static initialization */
94          setEncoding();          /* static initialization */
95          /* Separating out the file location from the rest of the
96           * initialisation, so the caller has the option of doing
97           * something else if a suitable file isn't found.
98           */
99          findFontConfigFile();
100     }
101 
102     public synchronized boolean init() {
103         if (!inited) {
104             this.preferLocaleFonts = false;
105             this.preferPropFonts = false;
106             setFontConfiguration();
107             readFontConfigFile(fontConfigFile);
108             initFontConfig();
109             inited = true;
110         }
111         return true;
112     }
113 
114     public FontConfiguration(SunFontManager fm,
115                              boolean preferLocaleFonts,
116                              boolean preferPropFonts) {
117         fontManager = fm;
118         if (FontUtilities.debugFonts()) {
119             FontUtilities.getLogger()
120                 .info("Creating alternate Font Configuration");
121         }
122         this.preferLocaleFonts = preferLocaleFonts;
123         this.preferPropFonts = preferPropFonts;
124         /* fontConfig should be initialised by default constructor, and
125          * its data tables can be shared, since readFontConfigFile doesn't
126          * update any other state. Also avoid a doPrivileged block.
127          */
128         initFontConfig();
129     }
130 
131     /**
132      * Fills in this instance's osVersion and osName members. By
133      * default uses the system properties os.name and os.version;
134      * subclasses may override.
135      */
136     protected void setOsNameAndVersion() {
137         osName = System.getProperty("os.name");
138         osVersion = System.getProperty("os.version");
139     }
140 
141     private void setEncoding() {
142         encoding = Charset.defaultCharset().name();
143         startupLocale = SunToolkit.getStartupLocale();
144     }
145 
146     /////////////////////////////////////////////////////////////////////
147     // methods for loading the FontConfig file                         //
148     /////////////////////////////////////////////////////////////////////
149 
150     public boolean foundOsSpecificFile() {
151         return foundOsSpecificFile;
152     }
153 
154     /* Smoke test to see if we can trust this configuration by testing if
155      * the first slot of a composite font maps to an installed file.
156      */
157     public boolean fontFilesArePresent() {
158         init();
159         short fontNameID = compFontNameIDs[0][0][0];
160         short fileNameID = getComponentFileID(fontNameID);
161         final String fileName = mapFileName(getComponentFileName(fileNameID));
162         Boolean exists = (Boolean)java.security.AccessController.doPrivileged(
163             new java.security.PrivilegedAction() {
164                  public Object run() {
165                      try {
166                          File f = new File(fileName);
167                          return Boolean.valueOf(f.exists());
168                      }
169                      catch (Exception e) {
170                          return false;
171                      }
172                  }
173                 });
174         return exists.booleanValue();
175     }
176 
177     private void findFontConfigFile() {
178 
179         foundOsSpecificFile = true; // default assumption.
180         String javaHome = System.getProperty("java.home");
181         if (javaHome == null) {
182             throw new Error("java.home property not set");
183         }
184         javaLib = javaHome + File.separator + "lib";
185         String userConfigFile = System.getProperty("sun.awt.fontconfig");
186         if (userConfigFile != null) {
187             fontConfigFile = new File(userConfigFile);
188         } else {
189             fontConfigFile = findFontConfigFile(javaLib);
190         }
191     }
192 
193     private void readFontConfigFile(File f) {
194         /* This is invoked here as readFontConfigFile is only invoked
195          * once per VM, and always in a privileged context, thus the
196          * directory containing installed fall back fonts is accessed
197          * from this context
198          */
199         getInstalledFallbackFonts(javaLib);
200 
201         if (f != null) {
202             try {
203                 FileInputStream in = new FileInputStream(f.getPath());
204                 if (isProperties) {
205                     loadProperties(in);
206                 } else {
207                     loadBinary(in);
208                 }
209                 in.close();
210                 if (FontUtilities.debugFonts()) {
211                     logger.config("Read logical font configuration from " + f);
212                 }
213             } catch (IOException e) {
214                 if (FontUtilities.debugFonts()) {
215                     logger.config("Failed to read logical font configuration from " + f);
216                 }
217             }
218         }
219         String version = getVersion();
220         if (!"1".equals(version) && FontUtilities.debugFonts()) {
221             logger.config("Unsupported fontconfig version: " + version);
222         }
223     }
224 
225     protected void getInstalledFallbackFonts(String javaLib) {
226         String fallbackDirName = javaLib + File.separator +
227             "fonts" + File.separator + "fallback";
228 
229         File fallbackDir = new File(fallbackDirName);
230         if (fallbackDir.exists() && fallbackDir.isDirectory()) {
231             String[] ttfs = fallbackDir.list(fontManager.getTrueTypeFilter());
232             String[] t1s = fallbackDir.list(fontManager.getType1Filter());
233             int numTTFs = (ttfs == null) ? 0 : ttfs.length;
234             int numT1s = (t1s == null) ? 0 : t1s.length;
235             int len = numTTFs + numT1s;
236             if (numTTFs + numT1s == 0) {
237                 return;
238             }
239             installedFallbackFontFiles = new String[len];
240             for (int i=0; i<numTTFs; i++) {
241                 installedFallbackFontFiles[i] =
242                     fallbackDir + File.separator + ttfs[i];
243             }
244             for (int i=0; i<numT1s; i++) {
245                 installedFallbackFontFiles[i+numTTFs] =
246                     fallbackDir + File.separator + t1s[i];
247             }
248             fontManager.registerFontsInDir(fallbackDirName);
249         }
250     }
251 
252     private File findImpl(String fname) {
253         File f = new File(fname + ".properties");
254         if (f.canRead()) {
255             isProperties = true;
256             return f;
257         }
258         f = new File(fname + ".bfc");
259         if (f.canRead()) {
260             isProperties = false;
261             return f;
262         }
263         return null;
264     }
265 
266     private File findFontConfigFile(String javaLib) {
267         String baseName = javaLib + File.separator + "fontconfig";
268         File configFile;
269         if (osVersion != null && osName != null) {
270             configFile = findImpl(baseName + "." + osName + "." + osVersion);
271             if (configFile != null) {
272                 return configFile;
273             }
274         }
275         if (osName != null) {
276             configFile = findImpl(baseName + "." + osName);
277             if (configFile != null) {
278                 return configFile;
279             }
280         }
281         if (osVersion != null) {
282             configFile = findImpl(baseName + "." + osVersion);
283             if (configFile != null) {
284                 return configFile;
285             }
286         }
287         foundOsSpecificFile = false;
288 
289         configFile = findImpl(baseName);
290         if (configFile != null) {
291             return configFile;
292         }
293         return null;
294     }
295 
296     /* Initialize the internal data tables from binary format font
297      * configuration file.
298      */
299     public static void loadBinary(InputStream inStream) throws IOException {
300         DataInputStream in = new DataInputStream(inStream);
301         head = readShortTable(in, HEAD_LENGTH);
302         int[] tableSizes = new int[INDEX_TABLEEND];
303         for (int i = 0; i < INDEX_TABLEEND; i++) {
304             tableSizes[i] = head[i + 1] - head[i];
305         }
306         table_scriptIDs       = readShortTable(in, tableSizes[INDEX_scriptIDs]);
307         table_scriptFonts     = readShortTable(in, tableSizes[INDEX_scriptFonts]);
308         table_elcIDs          = readShortTable(in, tableSizes[INDEX_elcIDs]);
309         table_sequences        = readShortTable(in, tableSizes[INDEX_sequences]);
310         table_fontfileNameIDs = readShortTable(in, tableSizes[INDEX_fontfileNameIDs]);
311         table_componentFontNameIDs = readShortTable(in, tableSizes[INDEX_componentFontNameIDs]);
312         table_filenames       = readShortTable(in, tableSizes[INDEX_filenames]);
313         table_awtfontpaths    = readShortTable(in, tableSizes[INDEX_awtfontpaths]);
314         table_exclusions      = readShortTable(in, tableSizes[INDEX_exclusions]);
315         table_proportionals   = readShortTable(in, tableSizes[INDEX_proportionals]);
316         table_scriptFontsMotif   = readShortTable(in, tableSizes[INDEX_scriptFontsMotif]);
317         table_alphabeticSuffix   = readShortTable(in, tableSizes[INDEX_alphabeticSuffix]);
318         table_stringIDs       = readShortTable(in, tableSizes[INDEX_stringIDs]);
319 
320         //StringTable cache
321         stringCache = new String[table_stringIDs.length + 1];
322 
323         int len = tableSizes[INDEX_stringTable];
324         byte[] bb = new byte[len * 2];
325         table_stringTable = new char[len];
326         in.read(bb);
327         int i = 0, j = 0;
328         while (i < len) {
329            table_stringTable[i++] = (char)(bb[j++] << 8 | (bb[j++] & 0xff));
330         }
331         if (verbose) {
332             dump();
333         }
334     }
335 
336     /* Generate a binary format font configuration from internal data
337      * tables.
338      */
339     public static void saveBinary(OutputStream out) throws IOException {
340         sanityCheck();
341 
342         DataOutputStream dataOut = new DataOutputStream(out);
343         writeShortTable(dataOut, head);
344         writeShortTable(dataOut, table_scriptIDs);
345         writeShortTable(dataOut, table_scriptFonts);
346         writeShortTable(dataOut, table_elcIDs);
347         writeShortTable(dataOut, table_sequences);
348         writeShortTable(dataOut, table_fontfileNameIDs);
349         writeShortTable(dataOut, table_componentFontNameIDs);
350         writeShortTable(dataOut, table_filenames);
351         writeShortTable(dataOut, table_awtfontpaths);
352         writeShortTable(dataOut, table_exclusions);
353         writeShortTable(dataOut, table_proportionals);
354         writeShortTable(dataOut, table_scriptFontsMotif);
355         writeShortTable(dataOut, table_alphabeticSuffix);
356         writeShortTable(dataOut, table_stringIDs);
357         //stringTable
358         dataOut.writeChars(new String(table_stringTable));
359         out.close();
360         if (verbose) {
361             dump();
362         }
363     }
364 
365     //private static boolean loadingProperties;
366     private static short stringIDNum;
367     private static short[] stringIDs;
368     private static StringBuilder stringTable;
369 
370     public static void loadProperties(InputStream in) throws IOException {
371         //loadingProperties = true;
372         //StringID starts from "1", "0" is reserved for "not defined"
373         stringIDNum = 1;
374         stringIDs = new short[1000];
375         stringTable = new StringBuilder(4096);
376 
377         if (verbose && logger == null) {
378             logger = PlatformLogger.getLogger("sun.awt.FontConfiguration");
379         }
380         new PropertiesHandler().load(in);
381 
382         //loadingProperties = false;
383         stringIDs = null;
384         stringTable = null;
385     }
386 
387 
388     /////////////////////////////////////////////////////////////////////
389     // methods for initializing the FontConfig                         //
390     /////////////////////////////////////////////////////////////////////
391 
392     /**
393      *  set initLocale, initEncoding and initELC for this FontConfig object
394      *  currently we just simply use the startup locale and encoding
395      */
396     private void initFontConfig() {
397         initLocale = startupLocale;
398         initEncoding = encoding;
399         if (preferLocaleFonts && !willReorderForStartupLocale()) {
400             preferLocaleFonts = false;
401         }
402         initELC = getInitELC();
403         initAllComponentFonts();
404     }
405 
406     //"ELC" stands for "Encoding.Language.Country". This method returns
407     //the ID of the matched elc setting of "initLocale" in elcIDs table.
408     //If no match is found, it returns the default ID, which is
409     //"NULL.NULL.NULL" in elcIDs table.
410     private short getInitELC() {
411         if (initELC != -1) {
412             return initELC;
413         }
414         HashMap <String, Integer> elcIDs = new HashMap<String, Integer>();
415         for (int i = 0; i < table_elcIDs.length; i++) {
416             elcIDs.put(getString(table_elcIDs[i]), i);
417         }
418         String language = initLocale.getLanguage();
419         String country = initLocale.getCountry();
420         String elc;
421         if (elcIDs.containsKey(elc=initEncoding + "." + language + "." + country)
422             || elcIDs.containsKey(elc=initEncoding + "." + language)
423             || elcIDs.containsKey(elc=initEncoding)) {
424             initELC = elcIDs.get(elc).shortValue();
425         } else {
426             initELC = elcIDs.get("NULL.NULL.NULL").shortValue();
427         }
428         int i = 0;
429         while (i < table_alphabeticSuffix.length) {
430             if (initELC == table_alphabeticSuffix[i]) {
431                 alphabeticSuffix = getString(table_alphabeticSuffix[i + 1]);
432                 return initELC;
433             }
434             i += 2;
435         }
436         return initELC;
437     }
438 
439     public static boolean verbose;
440     private short    initELC = -1;
441     private Locale   initLocale;
442     private String   initEncoding;
443     private String   alphabeticSuffix;
444 
445     private short[][][] compFontNameIDs = new short[NUM_FONTS][NUM_STYLES][];
446     private int[][][] compExclusions = new int[NUM_FONTS][][];
447     private int[] compCoreNum = new int[NUM_FONTS];
448 
449     private Set<Short> coreFontNameIDs = new HashSet<Short>();
450     private Set<Short> fallbackFontNameIDs = new HashSet<Short>();
451 
452     private void initAllComponentFonts() {
453         short[] fallbackScripts = getFallbackScripts();
454         for (int fontIndex = 0; fontIndex < NUM_FONTS; fontIndex++) {
455             short[] coreScripts = getCoreScripts(fontIndex);
456             compCoreNum[fontIndex] = coreScripts.length;
457             /*
458             System.out.println("coreScriptID=" + table_sequences[initELC * 5 + fontIndex]);
459             for (int i = 0; i < coreScripts.length; i++) {
460             System.out.println("  " + i + " :" + getString(table_scriptIDs[coreScripts[i]]));
461             }
462             */
463             //init exclusionRanges
464             int[][] exclusions = new int[coreScripts.length][];
465             for (int i = 0; i < coreScripts.length; i++) {
466                 exclusions[i] = getExclusionRanges(coreScripts[i]);
467             }
468             compExclusions[fontIndex] = exclusions;
469             //init componentFontNames
470             for (int styleIndex = 0; styleIndex < NUM_STYLES; styleIndex++) {
471                 int index;
472                 short[] nameIDs = new short[coreScripts.length + fallbackScripts.length];
473                 //core
474                 for (index = 0; index < coreScripts.length; index++) {
475                     nameIDs[index] = getComponentFontID(coreScripts[index],
476                                                fontIndex, styleIndex);
477                     if (preferLocaleFonts && localeMap != null &&
478                             fontManager.usingAlternateFontforJALocales()) {
479                         nameIDs[index] = remapLocaleMap(fontIndex, styleIndex,
480                                                         coreScripts[index], nameIDs[index]);
481                     }
482                     if (preferPropFonts) {
483                         nameIDs[index] = remapProportional(fontIndex, nameIDs[index]);
484                     }
485                     //System.out.println("nameid=" + nameIDs[index]);
486                     coreFontNameIDs.add(nameIDs[index]);
487                 }
488                 //fallback
489                 for (int i = 0; i < fallbackScripts.length; i++) {
490                     short id = getComponentFontID(fallbackScripts[i],
491                                                fontIndex, styleIndex);
492                     if (preferLocaleFonts && localeMap != null &&
493                             fontManager.usingAlternateFontforJALocales()) {
494                         id = remapLocaleMap(fontIndex, styleIndex, fallbackScripts[i], id);
495                     }
496                     if (preferPropFonts) {
497                         id = remapProportional(fontIndex, id);
498                     }
499                     if (contains(nameIDs, id, index)) {
500                         continue;
501                     }
502                     /*
503                       System.out.println("fontIndex=" + fontIndex + ", styleIndex=" + styleIndex
504                            + ", fbIndex=" + i + ",fbS=" + fallbackScripts[i] + ", id=" + id);
505                     */
506                     fallbackFontNameIDs.add(id);
507                     nameIDs[index++] = id;
508                 }
509                 if (index < nameIDs.length) {
510                     short[] newNameIDs = new short[index];
511                     System.arraycopy(nameIDs, 0, newNameIDs, 0, index);
512                     nameIDs = newNameIDs;
513                 }
514                 compFontNameIDs[fontIndex][styleIndex] = nameIDs;
515             }
516         }
517    }
518 
519    private short remapLocaleMap(int fontIndex, int styleIndex, short scriptID, short fontID) {
520         String scriptName = getString(table_scriptIDs[scriptID]);
521 
522         String value = (String)localeMap.get(scriptName);
523         if (value == null) {
524             String fontName = fontNames[fontIndex];
525             String styleName = styleNames[styleIndex];
526             value = (String)localeMap.get(fontName + "." + styleName + "." + scriptName);
527         }
528         if (value == null) {
529             return fontID;
530         }
531 
532         for (int i = 0; i < table_componentFontNameIDs.length; i++) {
533             String name = getString(table_componentFontNameIDs[i]);
534             if (value.equalsIgnoreCase(name)) {
535                 fontID = (short)i;
536                 break;
537             }
538         }
539         return fontID;
540     }
541 
542     public static boolean hasMonoToPropMap() {
543         return table_proportionals != null && table_proportionals.length != 0;
544     }
545 
546     private short remapProportional(int fontIndex, short id) {
547     if (preferPropFonts &&
548         table_proportionals.length != 0 &&
549         fontIndex != 2 &&         //"monospaced"
550         fontIndex != 4) {         //"dialoginput"
551             int i = 0;
552             while (i < table_proportionals.length) {
553                 if (table_proportionals[i] == id) {
554                     return table_proportionals[i + 1];
555                 }
556                 i += 2;
557             }
558         }
559         return id;
560     }
561 
562     /////////////////////////////////////////////////////////////////////
563     // Methods for handling font and style names                       //
564     /////////////////////////////////////////////////////////////////////
565     protected static final int NUM_FONTS = 5;
566     protected static final int NUM_STYLES = 4;
567     protected static final String[] fontNames
568             = {"serif", "sansserif", "monospaced", "dialog", "dialoginput"};
569     protected static final String[] publicFontNames
570             = {Font.SERIF, Font.SANS_SERIF, Font.MONOSPACED, Font.DIALOG,
571                Font.DIALOG_INPUT};
572     protected static final String[] styleNames
573             = {"plain", "bold", "italic", "bolditalic"};
574 
575     /**
576      * Checks whether the given font family name is a valid logical font name.
577      * The check is case insensitive.
578      */
579     public static boolean isLogicalFontFamilyName(String fontName) {
580         return isLogicalFontFamilyNameLC(fontName.toLowerCase(Locale.ENGLISH));
581     }
582 
583     /**
584      * Checks whether the given font family name is a valid logical font name.
585      * The check is case sensitive.
586      */
587     public static boolean isLogicalFontFamilyNameLC(String fontName) {
588         for (int i = 0; i < fontNames.length; i++) {
589             if (fontName.equals(fontNames[i])) {
590                 return true;
591             }
592         }
593         return false;
594     }
595 
596     /**
597      * Checks whether the given style name is a valid logical font style name.
598      */
599     private static boolean isLogicalFontStyleName(String styleName) {
600         for (int i = 0; i < styleNames.length; i++) {
601             if (styleName.equals(styleNames[i])) {
602                 return true;
603             }
604         }
605         return false;
606     }
607 
608     /**
609      * Checks whether the given font face name is a valid logical font name.
610      * The check is case insensitive.
611      */
612     public static boolean isLogicalFontFaceName(String fontName) {
613         return isLogicalFontFaceNameLC(fontName.toLowerCase(Locale.ENGLISH));
614     }
615 
616    /**
617     * Checks whether the given font face name is a valid logical font name.
618     * The check is case sensitive.
619     */
620     public static boolean isLogicalFontFaceNameLC(String fontName) {
621         int period = fontName.indexOf('.');
622         if (period >= 0) {
623             String familyName = fontName.substring(0, period);
624             String styleName = fontName.substring(period + 1);
625             return isLogicalFontFamilyName(familyName) &&
626                     isLogicalFontStyleName(styleName);
627         } else {
628             return isLogicalFontFamilyName(fontName);
629         }
630     }
631 
632     protected static int getFontIndex(String fontName) {
633         return getArrayIndex(fontNames, fontName);
634     }
635 
636     protected static int getStyleIndex(String styleName) {
637         return getArrayIndex(styleNames, styleName);
638     }
639 
640     private static int getArrayIndex(String[] names, String name) {
641         for (int i = 0; i < names.length; i++) {
642             if (name.equals(names[i])) {
643                 return i;
644             }
645         }
646         assert false;
647         return 0;
648     }
649 
650     protected static int getStyleIndex(int style) {
651         switch (style) {
652             case Font.PLAIN:
653                 return 0;
654             case Font.BOLD:
655                 return 1;
656             case Font.ITALIC:
657                 return 2;
658             case Font.BOLD | Font.ITALIC:
659                 return 3;
660             default:
661                 return 0;
662         }
663     }
664 
665     protected static String getFontName(int fontIndex) {
666         return fontNames[fontIndex];
667     }
668 
669     protected static String getStyleName(int styleIndex) {
670         return styleNames[styleIndex];
671     }
672 
673     /**
674      * Returns the font face name for the given logical font
675      * family name and style.
676      * The style argument is interpreted as in java.awt.Font.Font.
677      */
678     public static String getLogicalFontFaceName(String familyName, int style) {
679         assert isLogicalFontFamilyName(familyName);
680         return familyName.toLowerCase(Locale.ENGLISH) + "." + getStyleString(style);
681     }
682 
683     /**
684      * Returns the string typically used in properties files
685      * for the given style.
686      * The style argument is interpreted as in java.awt.Font.Font.
687      */
688     public static String getStyleString(int style) {
689         return getStyleName(getStyleIndex(style));
690     }
691 
692     /**
693      * Returns a fallback name for the given font name. For a few known
694      * font names, matching logical font names are returned. For all
695      * other font names, defaultFallback is returned.
696      * defaultFallback differs between AWT and 2D.
697      */
698     public abstract String getFallbackFamilyName(String fontName, String defaultFallback);
699 
700     /**
701      * Returns the 1.1 equivalent for some old 1.0 font family names for
702      * which we need to maintain compatibility in some configurations.
703      * Returns null for other font names.
704      */
705     protected String getCompatibilityFamilyName(String fontName) {
706         fontName = fontName.toLowerCase(Locale.ENGLISH);
707         if (fontName.equals("timesroman")) {
708             return "serif";
709         } else if (fontName.equals("helvetica")) {
710             return "sansserif";
711         } else if (fontName.equals("courier")) {
712             return "monospaced";
713         }
714         return null;
715     }
716 
717     protected static String[] installedFallbackFontFiles = null;
718 
719     /**
720      * Maps a file name given in the font configuration file
721      * to a format appropriate for the platform.
722      */
723     protected String mapFileName(String fileName) {
724         return fileName;
725     }
726 
727     //////////////////////////////////////////////////////////////////////
728     //  reordering                                                      //
729     //////////////////////////////////////////////////////////////////////
730 
731     /* Mappings from file encoding to font config name for font supporting
732      * the corresponding language. This is filled in by initReorderMap()
733      */
734     protected HashMap reorderMap = null;
735 
736     /* Platform-specific mappings */
737     protected abstract void initReorderMap();
738 
739     /* Move item at index "src" to "dst", shuffling all values in
740      * between down
741      */
742     private void shuffle(String[] seq, int src, int dst) {
743         if (dst >= src) {
744             return;
745         }
746         String tmp = seq[src];
747         for (int i=src; i>dst; i--) {
748             seq[i] = seq[i-1];
749         }
750         seq[dst] = tmp;
751     }
752 
753     /* Called to determine if there's a re-order sequence for this locale/
754      * encoding. If there's none then the caller can "bail" and avoid
755      * unnecessary work
756      */
757     public static boolean willReorderForStartupLocale() {
758         return getReorderSequence() != null;
759     }
760 
761     private static Object getReorderSequence() {
762         if (fontConfig.reorderMap == null) {
763              fontConfig.initReorderMap();
764         }
765         HashMap reorderMap = fontConfig.reorderMap;
766 
767         /* Find the most specific mapping */
768         String language = startupLocale.getLanguage();
769         String country = startupLocale.getCountry();
770         Object val = reorderMap.get(encoding + "." + language + "." + country);
771         if (val == null) {
772             val = reorderMap.get(encoding + "." + language);
773         }
774         if (val == null) {
775             val = reorderMap.get(encoding);
776         }
777         return val;
778     }
779 
780     /* This method reorders the sequence such that the matches for the
781      * file encoding are moved ahead of other elements.
782      * If an encoding uses more than one font, they are all moved up.
783      */
784      private void reorderSequenceForLocale(String[] seq) {
785         Object val =  getReorderSequence();
786         if (val instanceof String) {
787             for (int i=0; i< seq.length; i++) {
788                 if (seq[i].equals(val)) {
789                     shuffle(seq, i, 0);
790                     return;
791                 }
792             }
793         } else if (val instanceof String[]) {
794             String[] fontLangs = (String[])val;
795             for (int l=0; l<fontLangs.length;l++) {
796                 for (int i=0; i<seq.length;i++) {
797                     if (seq[i].equals(fontLangs[l])) {
798                         shuffle(seq, i, l);
799                     }
800                 }
801             }
802         }
803     }
804 
805     private static Vector splitSequence(String sequence) {
806         //String.split would be more convenient, but incurs big performance penalty
807         Vector parts = new Vector();
808         int start = 0;
809         int end;
810         while ((end = sequence.indexOf(',', start)) >= 0) {
811             parts.add(sequence.substring(start, end));
812             start = end + 1;
813         }
814         if (sequence.length() > start) {
815             parts.add(sequence.substring(start, sequence.length()));
816         }
817         return parts;
818     }
819 
820     protected String[] split(String sequence) {
821         Vector v = splitSequence(sequence);
822         return (String[])v.toArray(new String[0]);
823     }
824 
825     ////////////////////////////////////////////////////////////////////////
826     // Methods for extracting information from the fontconfig data for AWT//
827     ////////////////////////////////////////////////////////////////////////
828     private Hashtable charsetRegistry = new Hashtable(5);
829 
830     /**
831      * Returns FontDescriptors describing the physical fonts used for the
832      * given logical font name and style. The font name is interpreted
833      * in a case insensitive way.
834      * The style argument is interpreted as in java.awt.Font.Font.
835      */
836     public FontDescriptor[] getFontDescriptors(String fontName, int style) {
837         assert isLogicalFontFamilyName(fontName);
838         fontName = fontName.toLowerCase(Locale.ENGLISH);
839         int fontIndex = getFontIndex(fontName);
840         int styleIndex = getStyleIndex(style);
841         return getFontDescriptors(fontIndex, styleIndex);
842     }
843     private FontDescriptor[][][] fontDescriptors =
844         new FontDescriptor[NUM_FONTS][NUM_STYLES][];
845 
846     private FontDescriptor[] getFontDescriptors(int fontIndex, int styleIndex) {
847         FontDescriptor[] descriptors = fontDescriptors[fontIndex][styleIndex];
848         if (descriptors == null) {
849             descriptors = buildFontDescriptors(fontIndex, styleIndex);
850             fontDescriptors[fontIndex][styleIndex] = descriptors;
851         }
852         return descriptors;
853     }
854 
855     private FontDescriptor[] buildFontDescriptors(int fontIndex, int styleIndex) {
856         String fontName = fontNames[fontIndex];
857         String styleName = styleNames[styleIndex];
858 
859         short[] scriptIDs = getCoreScripts(fontIndex);
860         short[] nameIDs = compFontNameIDs[fontIndex][styleIndex];
861         String[] sequence = new String[scriptIDs.length];
862         String[] names = new String[scriptIDs.length];
863         for (int i = 0; i < sequence.length; i++) {
864             names[i] = getComponentFontName(nameIDs[i]);
865             sequence[i] = getScriptName(scriptIDs[i]);
866             if (alphabeticSuffix != null && "alphabetic".equals(sequence[i])) {
867                 sequence[i] = sequence[i] + "/" + alphabeticSuffix;
868             }
869         }
870         int[][] fontExclusionRanges = compExclusions[fontIndex];
871 
872         FontDescriptor[] descriptors = new FontDescriptor[names.length];
873 
874         for (int i = 0; i < names.length; i++) {
875             String awtFontName;
876             String encoding;
877 
878             awtFontName = makeAWTFontName(names[i], sequence[i]);
879 
880             // look up character encoding
881             encoding = getEncoding(names[i], sequence[i]);
882             if (encoding == null) {
883                 encoding = "default";
884             }
885             CharsetEncoder enc
886                     = getFontCharsetEncoder(encoding.trim(), awtFontName);
887 
888             // we already have the exclusion ranges
889             int[] exclusionRanges = fontExclusionRanges[i];
890 
891             // create descriptor
892             descriptors[i] = new FontDescriptor(awtFontName, enc, exclusionRanges);
893         }
894         return descriptors;
895     }
896 
897     /**
898      * Returns the AWT font name for the given platform font name and
899      * character subset.
900      */
901     protected String makeAWTFontName(String platformFontName,
902             String characterSubsetName) {
903         return platformFontName;
904     }
905 
906     /**
907      * Returns the java.io name of the platform character encoding for the
908      * given AWT font name and character subset. May return "default"
909      * to indicate that getDefaultFontCharset should be called to obtain
910      * a charset encoder.
911      */
912     protected abstract String getEncoding(String awtFontName,
913             String characterSubsetName);
914 
915     private CharsetEncoder getFontCharsetEncoder(final String charsetName,
916             String fontName) {
917 
918         Charset fc = null;
919         if (charsetName.equals("default")) {
920             fc = (Charset) charsetRegistry.get(fontName);
921         } else {
922             fc = (Charset) charsetRegistry.get(charsetName);
923         }
924         if (fc != null) {
925             return fc.newEncoder();
926         }
927 
928         if (!charsetName.startsWith("sun.awt.") && !charsetName.equals("default")) {
929             fc = Charset.forName(charsetName);
930         } else {
931             Class fcc = (Class) AccessController.doPrivileged(new PrivilegedAction() {
932                     public Object run() {
933                         try {
934                             return Class.forName(charsetName, true,
935                                                  Thread.currentThread().getContextClassLoader());
936                         } catch (ClassNotFoundException e) {
937                         }
938                         return null;
939                     }
940                 });
941 
942             if (fcc != null) {
943                 try {
944                     fc = (Charset) fcc.newInstance();
945                 } catch (Exception e) {
946                 }
947             }
948         }
949         if (fc == null) {
950             fc = getDefaultFontCharset(fontName);
951         }
952 
953         if (charsetName.equals("default")){
954             charsetRegistry.put(fontName, fc);
955         } else {
956             charsetRegistry.put(charsetName, fc);
957         }
958         return fc.newEncoder();
959     }
960 
961     protected abstract Charset getDefaultFontCharset(
962             String fontName);
963 
964     /* This retrieves the platform font directories (path) calculated
965      * by setAWTFontPathSequence(String[]). The default implementation
966      * returns null, its expected that X11 platforms may return
967      * non-null.
968      */
969     public HashSet<String> getAWTFontPathSet() {
970         return null;
971     }
972 
973     ////////////////////////////////////////////////////////////////////////
974     // methods for extracting information from the fontconfig data for 2D //
975     ////////////////////////////////////////////////////////////////////////
976 
977     /**
978      * Returns an array of composite font descriptors for all logical font
979      * faces.
980      * If the font configuration file doesn't specify Lucida Sans Regular
981      * or the given fallback font as component fonts, they are added here.
982      */
983     public CompositeFontDescriptor[] get2DCompositeFontInfo() {
984         CompositeFontDescriptor[] result =
985                 new CompositeFontDescriptor[NUM_FONTS * NUM_STYLES];
986         String defaultFontFile = fontManager.getDefaultFontFile();
987         String defaultFontFaceName = fontManager.getDefaultFontFaceName();
988 
989         for (int fontIndex = 0; fontIndex < NUM_FONTS; fontIndex++) {
990             String fontName = publicFontNames[fontIndex];
991 
992             // determine exclusion ranges for font
993             // AWT uses separate exclusion range array per component font.
994             // 2D packs all range boundaries into one array.
995             // Both use separate entries for lower and upper boundary.
996             int[][] exclusions = compExclusions[fontIndex];
997             int numExclusionRanges = 0;
998             for (int i = 0; i < exclusions.length; i++) {
999                 numExclusionRanges += exclusions[i].length;
1000             }
1001             int[] exclusionRanges = new int[numExclusionRanges];
1002             int[] exclusionRangeLimits = new int[exclusions.length];
1003             int exclusionRangeIndex = 0;
1004             int exclusionRangeLimitIndex = 0;
1005             for (int i = 0; i < exclusions.length; i++) {
1006                 int[] componentRanges = exclusions[i];
1007                 for (int j = 0; j < componentRanges.length; ) {
1008                     int value = componentRanges[j];
1009                     exclusionRanges[exclusionRangeIndex++] = componentRanges[j++];
1010                     exclusionRanges[exclusionRangeIndex++] = componentRanges[j++];
1011                 }
1012                 exclusionRangeLimits[i] = exclusionRangeIndex;
1013             }
1014             // other info is per style
1015             for (int styleIndex = 0; styleIndex < NUM_STYLES; styleIndex++) {
1016                 int maxComponentFontCount = compFontNameIDs[fontIndex][styleIndex].length;
1017                 boolean sawDefaultFontFile = false;
1018                 // fall back fonts listed in the lib/fonts/fallback directory
1019                 if (installedFallbackFontFiles != null) {
1020                     maxComponentFontCount += installedFallbackFontFiles.length;
1021                 }
1022                 String faceName = fontName + "." + styleNames[styleIndex];
1023 
1024                 // determine face names and file names of component fonts
1025                 String[] componentFaceNames = new String[maxComponentFontCount];
1026                 String[] componentFileNames = new String[maxComponentFontCount];
1027 
1028                 int index;
1029                 for (index = 0; index < compFontNameIDs[fontIndex][styleIndex].length; index++) {
1030                     short fontNameID = compFontNameIDs[fontIndex][styleIndex][index];
1031                     short fileNameID = getComponentFileID(fontNameID);
1032                     componentFaceNames[index] = getFaceNameFromComponentFontName(getComponentFontName(fontNameID));
1033                     componentFileNames[index] = mapFileName(getComponentFileName(fileNameID));
1034                     if (componentFileNames[index] == null ||
1035                         needToSearchForFile(componentFileNames[index])) {
1036                         componentFileNames[index] = getFileNameFromComponentFontName(getComponentFontName(fontNameID));
1037                     }
1038                     if (!sawDefaultFontFile &&
1039                         defaultFontFile.equals(componentFileNames[index])) {
1040                         sawDefaultFontFile = true;
1041                     }
1042                     /*
1043                     System.out.println(publicFontNames[fontIndex] + "." + styleNames[styleIndex] + "."
1044                         + getString(table_scriptIDs[coreScripts[index]]) + "=" + componentFileNames[index]);
1045                     */
1046                 }
1047 
1048                 //"Lucida Sans Regular" is not in the list, we add it here
1049                 if (!sawDefaultFontFile) {
1050                     int len = 0;
1051                     if (installedFallbackFontFiles != null) {
1052                         len = installedFallbackFontFiles.length;
1053                     }
1054                     if (index + len == maxComponentFontCount) {
1055                         String[] newComponentFaceNames = new String[maxComponentFontCount + 1];
1056                         System.arraycopy(componentFaceNames, 0, newComponentFaceNames, 0, index);
1057                         componentFaceNames = newComponentFaceNames;
1058                         String[] newComponentFileNames = new String[maxComponentFontCount + 1];
1059                         System.arraycopy(componentFileNames, 0, newComponentFileNames, 0, index);
1060                         componentFileNames = newComponentFileNames;
1061                     }
1062                     componentFaceNames[index] = defaultFontFaceName;
1063                     componentFileNames[index] = defaultFontFile;
1064                     index++;
1065                 }
1066 
1067                 if (installedFallbackFontFiles != null) {
1068                     for (int ifb=0; ifb<installedFallbackFontFiles.length; ifb++) {
1069                         componentFaceNames[index] = null;
1070                         componentFileNames[index] = installedFallbackFontFiles[ifb];
1071                         index++;
1072                     }
1073                 }
1074 
1075                 if (index < maxComponentFontCount) {
1076                     String[] newComponentFaceNames = new String[index];
1077                     System.arraycopy(componentFaceNames, 0, newComponentFaceNames, 0, index);
1078                     componentFaceNames = newComponentFaceNames;
1079                     String[] newComponentFileNames = new String[index];
1080                     System.arraycopy(componentFileNames, 0, newComponentFileNames, 0, index);
1081                     componentFileNames = newComponentFileNames;
1082                 }
1083                 // exclusion range limit array length must match component face name
1084                 // array length - native code relies on this
1085 
1086                 int[] clippedExclusionRangeLimits = exclusionRangeLimits;
1087                 if (index != clippedExclusionRangeLimits.length) {
1088                     int len = exclusionRangeLimits.length;
1089                     clippedExclusionRangeLimits = new int[index];
1090                     System.arraycopy(exclusionRangeLimits, 0, clippedExclusionRangeLimits, 0, len);
1091                     //padding for various fallback fonts
1092                     for (int i = len; i < index; i++) {
1093                         clippedExclusionRangeLimits[i] = exclusionRanges.length;
1094                     }
1095                 }
1096                 /*
1097                 System.out.println(faceName + ":");
1098                 for (int i = 0; i < componentFileNames.length; i++) {
1099                     System.out.println("    " + componentFaceNames[i]
1100                          + "  -> " + componentFileNames[i]);
1101                 }
1102                 */
1103                 result[fontIndex * NUM_STYLES + styleIndex]
1104                         = new CompositeFontDescriptor(
1105                             faceName,
1106                             compCoreNum[fontIndex],
1107                             componentFaceNames,
1108                             componentFileNames,
1109                             exclusionRanges,
1110                             clippedExclusionRangeLimits);
1111             }
1112         }
1113         return result;
1114     }
1115 
1116     protected abstract String getFaceNameFromComponentFontName(String componentFontName);
1117     protected abstract String getFileNameFromComponentFontName(String componentFontName);
1118 
1119     /*
1120     public class 2dFont {
1121         public String platformName;
1122         public String fontfileName;
1123     }
1124     private 2dFont [] componentFonts = null;
1125     */
1126 
1127     /* Used on Linux to test if a file referenced in a font configuration
1128      * file exists in the location that is expected. If it does, no need
1129      * to search for it. If it doesn't then unless its a fallback font,
1130      * return that expensive code should be invoked to search for the font.
1131      */
1132     HashMap<String, Boolean> existsMap;
1133     public boolean needToSearchForFile(String fileName) {
1134         if (!FontUtilities.isLinux) {
1135             return false;
1136         } else if (existsMap == null) {
1137            existsMap = new HashMap<String, Boolean>();
1138         }
1139         Boolean exists = existsMap.get(fileName);
1140         if (exists == null) {
1141             /* call getNumberCoreFonts() to ensure these are initialised, and
1142              * if this file isn't for a core component, ie, is a for a fallback
1143              * font which very typically isn't available, then can't afford
1144              * to take the start-up penalty to search for it.
1145              */
1146             getNumberCoreFonts();
1147             if (!coreFontFileNames.contains(fileName)) {
1148                 exists = Boolean.TRUE;
1149             } else {
1150                 exists = Boolean.valueOf((new File(fileName)).exists());
1151                 existsMap.put(fileName, exists);
1152                 if (FontUtilities.debugFonts() &&
1153                     exists == Boolean.FALSE) {
1154                     logger.warning("Couldn't locate font file " + fileName);
1155                 }
1156             }
1157         }
1158         return exists == Boolean.FALSE;
1159     }
1160 
1161     private int numCoreFonts = -1;
1162     private String[] componentFonts = null;
1163     HashMap <String, String> filenamesMap = new HashMap<String, String>();
1164     HashSet <String> coreFontFileNames = new HashSet<String>();
1165 
1166     /* Return the number of core fonts. Note this isn't thread safe but
1167      * a calling thread can call this and getPlatformFontNames() in either
1168      * order.
1169      */
1170     public int getNumberCoreFonts() {
1171         if (numCoreFonts == -1) {
1172             numCoreFonts = coreFontNameIDs.size();
1173             Short[] emptyShortArray = new Short[0];
1174             Short[] core = coreFontNameIDs.toArray(emptyShortArray);
1175             Short[] fallback = fallbackFontNameIDs.toArray(emptyShortArray);
1176 
1177             int numFallbackFonts = 0;
1178             int i;
1179             for (i = 0; i < fallback.length; i++) {
1180                 if (coreFontNameIDs.contains(fallback[i])) {
1181                     fallback[i] = null;
1182                     continue;
1183                 }
1184                 numFallbackFonts++;
1185             }
1186             componentFonts = new String[numCoreFonts + numFallbackFonts];
1187             String filename = null;
1188             for (i = 0; i < core.length; i++) {
1189                 short fontid = core[i];
1190                 short fileid = getComponentFileID(fontid);
1191                 componentFonts[i] = getComponentFontName(fontid);
1192                 String compFileName = getComponentFileName(fileid);
1193                 if (compFileName != null) {
1194                     coreFontFileNames.add(compFileName);
1195                 }
1196                 filenamesMap.put(componentFonts[i], mapFileName(compFileName));
1197             }
1198             for (int j = 0; j < fallback.length; j++) {
1199                 if (fallback[j] != null) {
1200                     short fontid = fallback[j];
1201                     short fileid = getComponentFileID(fontid);
1202                     componentFonts[i] = getComponentFontName(fontid);
1203                     filenamesMap.put(componentFonts[i],
1204                                      mapFileName(getComponentFileName(fileid)));
1205                     i++;
1206                 }
1207             }
1208         }
1209         return numCoreFonts;
1210     }
1211 
1212     /* Return all platform font names used by this font configuration.
1213      * The first getNumberCoreFonts() entries are guaranteed to be the
1214      * core fonts - ie no fall back only fonts.
1215      */
1216     public String[] getPlatformFontNames() {
1217         if (numCoreFonts == -1) {
1218             getNumberCoreFonts();
1219         }
1220         return componentFonts;
1221     }
1222 
1223     /**
1224      * Returns a file name for the physical font represented by this platform font name,
1225      * if the font configuration has such information available, or null if the
1226      * information is unavailable. The file name returned is just a hint; a null return
1227      * value doesn't necessarily mean that the font is unavailable, nor does a non-null
1228      * return value guarantee that the file exists and contains the physical font.
1229      * The file name can be an absolute or a relative path name.
1230      */
1231     public String getFileNameFromPlatformName(String platformName) {
1232         // get2DCompositeFontInfo
1233         //     ->  getFileNameFromComponentfontName()  (W/M)
1234         //       ->   getFileNameFromPlatformName()
1235         // it's a waste of time on Win32, but I have to give X11 a chance to
1236         // call getFileNameFromXLFD()
1237         return filenamesMap.get(platformName);
1238     }
1239 
1240     /**
1241      * Returns a configuration specific path to be appended to the font
1242      * search path.
1243      */
1244     public String getExtraFontPath() {
1245         return getString(head[INDEX_appendedfontpath]);
1246     }
1247 
1248     public String getVersion() {
1249         return getString(head[INDEX_version]);
1250     }
1251 
1252     /* subclass support */
1253     protected static FontConfiguration getFontConfiguration() {
1254         return fontConfig;
1255     }
1256 
1257     protected void setFontConfiguration() {
1258         fontConfig = this;      /* static initialization */
1259     }
1260 
1261     //////////////////////////////////////////////////////////////////////
1262     // FontConfig data tables and the index constants in binary file    //
1263     //////////////////////////////////////////////////////////////////////
1264     /* The binary font configuration file begins with a short[] "head", which
1265      * contains the offsets to the starts of the individual data table which
1266      * immediately follow. Teh current implemention includes the tables shown
1267      * below.
1268      *
1269      * (00) table_scriptIDs    :stringIDs of all defined CharacterSubsetNames
1270      * (01) table_scriptFonts  :scriptID x fontIndex x styleIndex->
1271      *                          PlatformFontNameID mapping. Each scriptID might
1272      *                          have 1 or 20 entries depends on if it is defined
1273      *                          via a "allfonts.CharacterSubsetname" or a list of
1274      *                          "LogicalFontName.StyleName.CharacterSubsetName"
1275      *                          entries, positive entry means it's a "allfonts"
1276      *                          entry, a negative value means this is a offset to
1277      *                          a NUM_FONTS x NUM_STYLES subtable.
1278      * (02) table_elcIDs       :stringIDs of all defined ELC names, string
1279      *                          "NULL.NULL.NULL" is used for "default"
1280      * (03) table_sequences    :elcID x logicalFont -> scriptIDs table defined
1281      *                          by "sequence.allfonts/LogicalFontName.ELC" in
1282      *                          font configuration file, each "elcID" has
1283      *                          NUM_FONTS (5) entries in this table.
1284      * (04) table_fontfileNameIDs
1285      *                         :stringIDs of all defined font file names
1286      * (05) table_componentFontNameIDs
1287      *                         :stringIDs of all defined PlatformFontNames
1288      * (06) table_filenames    :platformFontNamesID->fontfileNameID mapping
1289      *                          table, the index is the platformFontNamesID.
1290      * (07) table_awtfontpaths :CharacterSubsetNames->awtfontpaths mapping table,
1291      *                          the index is the CharacterSubsetName's stringID
1292      *                          and content is the stringID of awtfontpath.
1293      * (08) table_exclusions   :scriptID -> exclusionRanges mapping table,
1294      *                          the index is the scriptID and the content is
1295                                 a id of an exclusionRanges int[].
1296      * (09) table_proportionals:list of pairs of PlatformFontNameIDs, stores
1297      *                          the replacement info defined by "proportional"
1298      *                          keyword.
1299      * (10) table_scriptFontsMotif
1300      *                         :same as (01) except this table stores the
1301      *                          info defined with ".motif" keyword
1302      * (11) table_alphabeticSuffix
1303      *                         :elcID -> stringID of alphabetic/XXXX entries
1304      * (12) table_stringIDs    :The index of this table is the string ID, the
1305      *                          content is the "start index" of this string in
1306      *                          stringTable, use the start index of next entry
1307      *                          as the "end index".
1308      * (13) table_stringTable  :The real storage of all character strings defined
1309      *                          /used this font configuration, need a pair of
1310      *                          "start" and "end" indices to access.
1311      * (14) reserved
1312      * (15) table_fallbackScripts
1313      *                         :stringIDs of fallback CharacterSubsetnames, stored
1314      *                          in the order of they are defined in sequence.fallback.
1315      * (16) table_appendedfontpath
1316      *                         :stringtID of the "appendedfontpath" defined.
1317      * (17) table_version   :stringID of the version number of this fontconfig file.
1318      */
1319     private static final int HEAD_LENGTH = 20;
1320     private static final int INDEX_scriptIDs = 0;
1321     private static final int INDEX_scriptFonts = 1;
1322     private static final int INDEX_elcIDs = 2;
1323     private static final int INDEX_sequences = 3;
1324     private static final int INDEX_fontfileNameIDs = 4;
1325     private static final int INDEX_componentFontNameIDs = 5;
1326     private static final int INDEX_filenames = 6;
1327     private static final int INDEX_awtfontpaths = 7;
1328     private static final int INDEX_exclusions = 8;
1329     private static final int INDEX_proportionals = 9;
1330     private static final int INDEX_scriptFontsMotif = 10;
1331     private static final int INDEX_alphabeticSuffix = 11;
1332     private static final int INDEX_stringIDs = 12;
1333     private static final int INDEX_stringTable = 13;
1334     private static final int INDEX_TABLEEND = 14;
1335     private static final int INDEX_fallbackScripts = 15;
1336     private static final int INDEX_appendedfontpath = 16;
1337     private static final int INDEX_version = 17;
1338 
1339     private static short[] head;
1340     private static short[] table_scriptIDs;
1341     private static short[] table_scriptFonts;
1342     private static short[] table_elcIDs;
1343     private static short[] table_sequences;
1344     private static short[] table_fontfileNameIDs;
1345     private static short[] table_componentFontNameIDs;
1346     private static short[] table_filenames;
1347     protected static short[] table_awtfontpaths;
1348     private static short[] table_exclusions;
1349     private static short[] table_proportionals;
1350     private static short[] table_scriptFontsMotif;
1351     private static short[] table_alphabeticSuffix;
1352     private static short[] table_stringIDs;
1353     private static char[]  table_stringTable;
1354 
1355     /**
1356      * Checks consistencies of complied fontconfig data. This method
1357      * is called only at the build-time from
1358      * build.tools.compilefontconfig.CompileFontConfig.
1359      */
1360     private static void sanityCheck() {
1361         int errors = 0;
1362 
1363         //This method will only be called during build time, do we
1364         //need do PrivilegedAction?
1365         String osName = (String)java.security.AccessController.doPrivileged(
1366                             new java.security.PrivilegedAction() {
1367             public Object run() {
1368                 return System.getProperty("os.name");
1369             }
1370         });
1371 
1372         //componentFontNameID starts from "1"
1373         for (int ii = 1; ii < table_filenames.length; ii++) {
1374             if (table_filenames[ii] == -1) {
1375                 // The corresponding finename entry for a component
1376                 // font name is mandatory on Windows, but it's
1377                 // optional on Solaris and Linux.
1378                 if (osName.contains("Windows")) {
1379                     System.err.println("\n Error: <filename."
1380                                        + getString(table_componentFontNameIDs[ii])
1381                                        + "> entry is missing!!!");
1382                     errors++;
1383                 } else {
1384                     if (verbose && !isEmpty(table_filenames)) {
1385                         System.err.println("\n Note: 'filename' entry is undefined for \""
1386                                            + getString(table_componentFontNameIDs[ii])
1387                                            + "\"");
1388                     }
1389                 }
1390             }
1391         }
1392         for (int ii = 0; ii < table_scriptIDs.length; ii++) {
1393             short fid = table_scriptFonts[ii];
1394             if (fid == 0) {
1395                 System.out.println("\n Error: <allfonts."
1396                                    + getString(table_scriptIDs[ii])
1397                                    + "> entry is missing!!!");
1398                 errors++;
1399                 continue;
1400             } else if (fid < 0) {
1401                 fid = (short)-fid;
1402                 for (int iii = 0; iii < NUM_FONTS; iii++) {
1403                     for (int iij = 0; iij < NUM_STYLES; iij++) {
1404                         int jj = iii * NUM_STYLES + iij;
1405                         short ffid = table_scriptFonts[fid + jj];
1406                         if (ffid == 0) {
1407                             System.err.println("\n Error: <"
1408                                            + getFontName(iii) + "."
1409                                            + getStyleName(iij) + "."
1410                                            + getString(table_scriptIDs[ii])
1411                                            + "> entry is missing!!!");
1412                             errors++;
1413                         }
1414                     }
1415                 }
1416             }
1417         }
1418         if ("SunOS".equals(osName)) {
1419             for (int ii = 0; ii < table_awtfontpaths.length; ii++) {
1420                 if (table_awtfontpaths[ii] == 0) {
1421                     String script = getString(table_scriptIDs[ii]);
1422                     if (script.contains("lucida") ||
1423                         script.contains("dingbats") ||
1424                         script.contains("symbol")) {
1425                         continue;
1426                     }
1427                     System.err.println("\nError: "
1428                                        + "<awtfontpath."
1429                                        + script
1430                                        + "> entry is missing!!!");
1431                     errors++;
1432                 }
1433             }
1434         }
1435         if (errors != 0) {
1436             System.err.println("!!THERE ARE " + errors + " ERROR(S) IN "
1437                                + "THE FONTCONFIG FILE, PLEASE CHECK ITS CONTENT!!\n");
1438             System.exit(1);
1439         }
1440     }
1441 
1442     private static boolean isEmpty(short[] a) {
1443         for (short s : a) {
1444             if (s != -1) {
1445                 return false;
1446             }
1447         }
1448         return true;
1449     }
1450 
1451     //dump the fontconfig data tables
1452     private static void dump() {
1453         System.out.println("\n----Head Table------------");
1454         for (int ii = 0; ii < HEAD_LENGTH; ii++) {
1455             System.out.println("  " + ii + " : " + head[ii]);
1456         }
1457         System.out.println("\n----scriptIDs-------------");
1458         printTable(table_scriptIDs, 0);
1459         System.out.println("\n----scriptFonts----------------");
1460         for (int ii = 0; ii < table_scriptIDs.length; ii++) {
1461             short fid = table_scriptFonts[ii];
1462             if (fid >= 0) {
1463                 System.out.println("  allfonts."
1464                                    + getString(table_scriptIDs[ii])
1465                                    + "="
1466                                    + getString(table_componentFontNameIDs[fid]));
1467             }
1468         }
1469         for (int ii = 0; ii < table_scriptIDs.length; ii++) {
1470             short fid = table_scriptFonts[ii];
1471             if (fid < 0) {
1472                 fid = (short)-fid;
1473                 for (int iii = 0; iii < NUM_FONTS; iii++) {
1474                     for (int iij = 0; iij < NUM_STYLES; iij++) {
1475                         int jj = iii * NUM_STYLES + iij;
1476                         short ffid = table_scriptFonts[fid + jj];
1477                         System.out.println("  "
1478                                            + getFontName(iii) + "."
1479                                            + getStyleName(iij) + "."
1480                                            + getString(table_scriptIDs[ii])
1481                                            + "="
1482                                            + getString(table_componentFontNameIDs[ffid]));
1483                     }
1484                 }
1485 
1486             }
1487         }
1488         System.out.println("\n----elcIDs----------------");
1489         printTable(table_elcIDs, 0);
1490         System.out.println("\n----sequences-------------");
1491         for (int ii = 0; ii< table_elcIDs.length; ii++) {
1492             System.out.println("  " + ii + "/" + getString((short)table_elcIDs[ii]));
1493             short[] ss = getShortArray(table_sequences[ii * NUM_FONTS + 0]);
1494             for (int jj = 0; jj < ss.length; jj++) {
1495                 System.out.println("     " + getString((short)table_scriptIDs[ss[jj]]));
1496             }
1497         }
1498         System.out.println("\n----fontfileNameIDs-------");
1499         printTable(table_fontfileNameIDs, 0);
1500 
1501         System.out.println("\n----componentFontNameIDs--");
1502         printTable(table_componentFontNameIDs, 1);
1503         System.out.println("\n----filenames-------------");
1504         for (int ii = 0; ii < table_filenames.length; ii++) {
1505             if (table_filenames[ii] == -1) {
1506                 System.out.println("  " + ii + " : null");
1507             } else {
1508                 System.out.println("  " + ii + " : "
1509                    + getString(table_fontfileNameIDs[table_filenames[ii]]));
1510             }
1511         }
1512         System.out.println("\n----awtfontpaths---------");
1513         for (int ii = 0; ii < table_awtfontpaths.length; ii++) {
1514             System.out.println("  " + getString(table_scriptIDs[ii])
1515                                + " : "
1516                                + getString(table_awtfontpaths[ii]));
1517         }
1518         System.out.println("\n----proportionals--------");
1519         for (int ii = 0; ii < table_proportionals.length; ii++) {
1520             System.out.println("  "
1521                    + getString((short)table_componentFontNameIDs[table_proportionals[ii++]])
1522                    + " -> "
1523                    + getString((short)table_componentFontNameIDs[table_proportionals[ii]]));
1524         }
1525         int i = 0;
1526         System.out.println("\n----alphabeticSuffix----");
1527         while (i < table_alphabeticSuffix.length) {
1528           System.out.println("    " + getString(table_elcIDs[table_alphabeticSuffix[i++]])
1529                              + " -> " + getString(table_alphabeticSuffix[i++]));
1530         }
1531         System.out.println("\n----String Table---------");
1532         System.out.println("    stringID:    Num =" + table_stringIDs.length);
1533         System.out.println("    stringTable: Size=" + table_stringTable.length * 2);
1534 
1535         System.out.println("\n----fallbackScriptIDs---");
1536         short[] fbsIDs = getShortArray(head[INDEX_fallbackScripts]);
1537         for (int ii = 0; ii < fbsIDs.length; ii++) {
1538           System.out.println("  " + getString(table_scriptIDs[fbsIDs[ii]]));
1539         }
1540         System.out.println("\n----appendedfontpath-----");
1541         System.out.println("  " + getString(head[INDEX_appendedfontpath]));
1542         System.out.println("\n----Version--------------");
1543         System.out.println("  " + getString(head[INDEX_version]));
1544     }
1545 
1546 
1547     //////////////////////////////////////////////////////////////////////
1548     // Data table access methods                                        //
1549     //////////////////////////////////////////////////////////////////////
1550 
1551     /* Return the fontID of the platformFontName defined in this font config
1552      * by "LogicalFontName.StyleName.CharacterSubsetName" entry or
1553      * "allfonts.CharacterSubsetName" entry in properties format fc file.
1554      */
1555     protected static short getComponentFontID(short scriptID, int fontIndex, int styleIndex) {
1556         short fid = table_scriptFonts[scriptID];
1557         //System.out.println("fid=" + fid + "/ scriptID=" + scriptID + ", fi=" + fontIndex + ", si=" + styleIndex);
1558         if (fid >= 0) {
1559             //"allfonts"
1560             return fid;
1561         } else {
1562             return table_scriptFonts[-fid + fontIndex * NUM_STYLES + styleIndex];
1563         }
1564     }
1565 
1566     /* Same as getCompoentFontID() except this method returns the fontID define by
1567      * "xxxx.motif" entry.
1568      */
1569     protected static short getComponentFontIDMotif(short scriptID, int fontIndex, int styleIndex) {
1570         if (table_scriptFontsMotif.length == 0) {
1571             return 0;
1572         }
1573         short fid = table_scriptFontsMotif[scriptID];
1574         if (fid >= 0) {
1575             //"allfonts" > 0 or "not defined" == 0
1576             return fid;
1577         } else {
1578             return table_scriptFontsMotif[-fid + fontIndex * NUM_STYLES + styleIndex];
1579         }
1580     }
1581 
1582     private static int[] getExclusionRanges(short scriptID) {
1583         short exID = table_exclusions[scriptID];
1584         if (exID == 0) {
1585             return EMPTY_INT_ARRAY;
1586         } else {
1587             char[] exChar = getString(exID).toCharArray();
1588             int[] exInt = new int[exChar.length / 2];
1589             int i = 0;
1590             for (int j = 0; j < exInt.length; j++) {
1591                 exInt[j] = (exChar[i++] << 16) + (exChar[i++] & 0xffff);
1592             }
1593             return exInt;
1594         }
1595     }
1596 
1597     private static boolean contains(short IDs[], short id, int limit) {
1598         for (int i = 0; i < limit; i++) {
1599             if (IDs[i] == id) {
1600                 return true;
1601             }
1602         }
1603         return false;
1604     }
1605 
1606     /* Return the PlatformFontName from its fontID*/
1607     protected static String getComponentFontName(short id) {
1608         if (id < 0) {
1609             return null;
1610         }
1611         return getString(table_componentFontNameIDs[id]);
1612     }
1613 
1614     private static String getComponentFileName(short id) {
1615         if (id < 0) {
1616             return null;
1617         }
1618         return getString(table_fontfileNameIDs[id]);
1619     }
1620 
1621     //componentFontID -> componentFileID
1622     private static short getComponentFileID(short nameID) {
1623         return table_filenames[nameID];
1624     }
1625 
1626     private static String getScriptName(short scriptID) {
1627         return getString(table_scriptIDs[scriptID]);
1628     }
1629 
1630    private HashMap<String, Short> reorderScripts;
1631    protected short[] getCoreScripts(int fontIndex) {
1632         short elc = getInitELC();
1633         /*
1634           System.out.println("getCoreScripts: elc=" + elc + ", fontIndex=" + fontIndex);
1635           short[] ss = getShortArray(table_sequences[elc * NUM_FONTS + fontIndex]);
1636           for (int i = 0; i < ss.length; i++) {
1637               System.out.println("     " + getString((short)table_scriptIDs[ss[i]]));
1638           }
1639           */
1640         short[] scripts = getShortArray(table_sequences[elc * NUM_FONTS + fontIndex]);
1641         if (preferLocaleFonts) {
1642             if (reorderScripts == null) {
1643                 reorderScripts = new HashMap<String, Short>();
1644             }
1645             String[] ss = new String[scripts.length];
1646             for (int i = 0; i < ss.length; i++) {
1647                 ss[i] = getScriptName(scripts[i]);
1648                 reorderScripts.put(ss[i], scripts[i]);
1649             }
1650             reorderSequenceForLocale(ss);
1651             for (int i = 0; i < ss.length; i++) {
1652                 scripts[i] = reorderScripts.get(ss[i]);
1653             }
1654         }
1655          return scripts;
1656     }
1657 
1658     private static short[] getFallbackScripts() {
1659         return getShortArray(head[INDEX_fallbackScripts]);
1660     }
1661 
1662     private static void printTable(short[] list, int start) {
1663         for (int i = start; i < list.length; i++) {
1664             System.out.println("  " + i + " : " + getString(list[i]));
1665         }
1666     }
1667 
1668     private static short[] readShortTable(DataInputStream in, int len )
1669         throws IOException {
1670         if (len == 0) {
1671             return EMPTY_SHORT_ARRAY;
1672         }
1673         short[] data = new short[len];
1674         byte[] bb = new byte[len * 2];
1675         in.read(bb);
1676         int i = 0,j = 0;
1677         while (i < len) {
1678             data[i++] = (short)(bb[j++] << 8 | (bb[j++] & 0xff));
1679         }
1680         return data;
1681     }
1682 
1683     private static void writeShortTable(DataOutputStream out, short[] data)
1684         throws IOException {
1685         for (short val : data) {
1686             out.writeShort(val);
1687         }
1688     }
1689 
1690     private static short[] toList(HashMap<String, Short> map) {
1691         short[] list = new short[map.size()];
1692         Arrays.fill(list, (short) -1);
1693         for (Entry<String, Short> entry : map.entrySet()) {
1694             list[entry.getValue()] = getStringID(entry.getKey());
1695         }
1696         return list;
1697     }
1698 
1699     //runtime cache
1700     private static String[] stringCache;
1701     protected static String getString(short stringID) {
1702         if (stringID == 0)
1703             return null;
1704         /*
1705         if (loadingProperties) {
1706             return stringTable.substring(stringIDs[stringID],
1707                                          stringIDs[stringID+1]);
1708         }
1709         */
1710         //sync if we want it to be MT-enabled
1711         if (stringCache[stringID] == null){
1712             stringCache[stringID] =
1713               new String (table_stringTable,
1714                           table_stringIDs[stringID],
1715                           table_stringIDs[stringID+1] - table_stringIDs[stringID]);
1716         }
1717         return stringCache[stringID];
1718     }
1719 
1720     private static short[] getShortArray(short shortArrayID) {
1721         String s = getString(shortArrayID);
1722         char[] cc = s.toCharArray();
1723         short[] ss = new short[cc.length];
1724         for (int i = 0; i < cc.length; i++) {
1725             ss[i] = (short)(cc[i] & 0xffff);
1726         }
1727         return ss;
1728     }
1729 
1730     private static short getStringID(String s) {
1731         if (s == null) {
1732             return (short)0;
1733         }
1734         short pos0 = (short)stringTable.length();
1735         stringTable.append(s);
1736         short pos1 = (short)stringTable.length();
1737 
1738         stringIDs[stringIDNum] = pos0;
1739         stringIDs[stringIDNum + 1] = pos1;
1740         stringIDNum++;
1741         if (stringIDNum + 1 >= stringIDs.length) {
1742             short[] tmp = new short[stringIDNum + 1000];
1743             System.arraycopy(stringIDs, 0, tmp, 0, stringIDNum);
1744             stringIDs = tmp;
1745         }
1746         return (short)(stringIDNum - 1);
1747     }
1748 
1749     private static short getShortArrayID(short sa[]) {
1750         char[] cc = new char[sa.length];
1751         for (int i = 0; i < sa.length; i ++) {
1752             cc[i] = (char)sa[i];
1753         }
1754         String s = new String(cc);
1755         return getStringID(s);
1756     }
1757 
1758     //utility "empty" objects
1759     private static final int[] EMPTY_INT_ARRAY = new int[0];
1760     private static final String[] EMPTY_STRING_ARRAY = new String[0];
1761     private static final short[] EMPTY_SHORT_ARRAY = new short[0];
1762     private static final String UNDEFINED_COMPONENT_FONT = "unknown";
1763 
1764     //////////////////////////////////////////////////////////////////////////
1765     //Convert the FontConfig data in Properties file to binary data tables  //
1766     //////////////////////////////////////////////////////////////////////////
1767     static class PropertiesHandler {
1768         public void load(InputStream in) throws IOException {
1769             initLogicalNameStyle();
1770             initHashMaps();
1771             FontProperties fp = new FontProperties();
1772             fp.load(in);
1773             initBinaryTable();
1774         }
1775 
1776         private void initBinaryTable() {
1777             //(0)
1778             head = new short[HEAD_LENGTH];
1779             head[INDEX_scriptIDs] = (short)HEAD_LENGTH;
1780 
1781             table_scriptIDs = toList(scriptIDs);
1782             //(1)a: scriptAllfonts scriptID/allfonts -> componentFontNameID
1783             //   b: scriptFonts    scriptID -> componentFontNameID[20]
1784             //if we have a "allfonts.script" def, then we just put
1785             //the "-platformFontID" value in the slot, otherwise the slot
1786             //value is "offset" which "offset" is where 20 entries located
1787             //in the table attached.
1788             head[INDEX_scriptFonts] = (short)(head[INDEX_scriptIDs]  + table_scriptIDs.length);
1789             int len = table_scriptIDs.length + scriptFonts.size() * 20;
1790             table_scriptFonts = new short[len];
1791 
1792             for (Entry<Short, Short> entry : scriptAllfonts.entrySet()) {
1793                 table_scriptFonts[entry.getKey().intValue()] = entry.getValue();
1794             }
1795             int off = table_scriptIDs.length;
1796             for (Entry<Short, Short[]> entry : scriptFonts.entrySet()) {
1797                 table_scriptFonts[entry.getKey().intValue()] = (short)-off;
1798                 Short[] v = entry.getValue();
1799                 for (int i = 0; i < 20; i++) {
1800                     if (v[i] != null) {
1801                         table_scriptFonts[off++] = v[i];
1802                     } else {
1803                         table_scriptFonts[off++] = 0;
1804                     }
1805                 }
1806             }
1807 
1808             //(2)
1809             head[INDEX_elcIDs] = (short)(head[INDEX_scriptFonts]  + table_scriptFonts.length);
1810             table_elcIDs = toList(elcIDs);
1811 
1812             //(3) sequences  elcID -> XXXX[1|5] -> scriptID[]
1813             head[INDEX_sequences] = (short)(head[INDEX_elcIDs]  + table_elcIDs.length);
1814             table_sequences = new short[elcIDs.size() * NUM_FONTS];
1815             for (Entry<Short, short[]> entry : sequences.entrySet()) {
1816                 //table_sequences[entry.getKey().intValue()] = (short)-off;
1817                 int k = entry.getKey().intValue();
1818                 short[] v = entry.getValue();
1819                 /*
1820                   System.out.println("elc=" + k + "/" + getString((short)table_elcIDs[k]));
1821                   short[] ss = getShortArray(v[0]);
1822                   for (int i = 0; i < ss.length; i++) {
1823                   System.out.println("     " + getString((short)table_scriptIDs[ss[i]]));
1824                   }
1825                   */
1826                 if (v.length == 1) {
1827                     //the "allfonts" entries
1828                     for (int i = 0; i < NUM_FONTS; i++) {
1829                         table_sequences[k * NUM_FONTS + i] = v[0];
1830                     }
1831                 } else {
1832                     for (int i = 0; i < NUM_FONTS; i++) {
1833                         table_sequences[k * NUM_FONTS + i] = v[i];
1834                     }
1835                 }
1836             }
1837             //(4)
1838             head[INDEX_fontfileNameIDs] = (short)(head[INDEX_sequences]  + table_sequences.length);
1839             table_fontfileNameIDs = toList(fontfileNameIDs);
1840 
1841             //(5)
1842             head[INDEX_componentFontNameIDs] = (short)(head[INDEX_fontfileNameIDs]  + table_fontfileNameIDs.length);
1843             table_componentFontNameIDs = toList(componentFontNameIDs);
1844 
1845             //(6)componentFontNameID -> filenameID
1846             head[INDEX_filenames] = (short)(head[INDEX_componentFontNameIDs]  + table_componentFontNameIDs.length);
1847             table_filenames = new short[table_componentFontNameIDs.length];
1848             Arrays.fill(table_filenames, (short) -1);
1849 
1850             for (Entry<Short, Short> entry : filenames.entrySet()) {
1851                 table_filenames[entry.getKey()] = entry.getValue();
1852             }
1853 
1854             //(7)scriptID-> awtfontpath
1855             //the paths are stored as scriptID -> stringID in awtfontpahts
1856             head[INDEX_awtfontpaths] = (short)(head[INDEX_filenames]  + table_filenames.length);
1857             table_awtfontpaths = new short[table_scriptIDs.length];
1858             for (Entry<Short, Short> entry : awtfontpaths.entrySet()) {
1859                 table_awtfontpaths[entry.getKey()] = entry.getValue();
1860             }
1861 
1862             //(8)exclusions
1863             head[INDEX_exclusions] = (short)(head[INDEX_awtfontpaths]  + table_awtfontpaths.length);
1864             table_exclusions = new short[scriptIDs.size()];
1865             for (Entry<Short, int[]> entry : exclusions.entrySet()) {
1866                 int[] exI = entry.getValue();
1867                 char[] exC = new char[exI.length * 2];
1868                 int j = 0;
1869                 for (int i = 0; i < exI.length; i++) {
1870                     exC[j++] = (char) (exI[i] >> 16);
1871                     exC[j++] = (char) (exI[i] & 0xffff);
1872                 }
1873                 table_exclusions[entry.getKey()] = getStringID(new String (exC));
1874             }
1875             //(9)proportionals
1876             head[INDEX_proportionals] = (short)(head[INDEX_exclusions]  + table_exclusions.length);
1877             table_proportionals = new short[proportionals.size() * 2];
1878             int j = 0;
1879             for (Entry<Short, Short> entry : proportionals.entrySet()) {
1880                 table_proportionals[j++] = entry.getKey();
1881                 table_proportionals[j++] = entry.getValue();
1882             }
1883 
1884             //(10) see (1) for info, the only difference is "xxx.motif"
1885             head[INDEX_scriptFontsMotif] = (short)(head[INDEX_proportionals] + table_proportionals.length);
1886             if (scriptAllfontsMotif.size() != 0 || scriptFontsMotif.size() != 0) {
1887                 len = table_scriptIDs.length + scriptFontsMotif.size() * 20;
1888                 table_scriptFontsMotif = new short[len];
1889 
1890                 for (Entry<Short, Short> entry : scriptAllfontsMotif.entrySet()) {
1891                     table_scriptFontsMotif[entry.getKey().intValue()] =
1892                       (short)entry.getValue();
1893                 }
1894                 off = table_scriptIDs.length;
1895                 for (Entry<Short, Short[]> entry : scriptFontsMotif.entrySet()) {
1896                     table_scriptFontsMotif[entry.getKey().intValue()] = (short)-off;
1897                     Short[] v = entry.getValue();
1898                     int i = 0;
1899                     while (i < 20) {
1900                         if (v[i] != null) {
1901                             table_scriptFontsMotif[off++] = v[i];
1902                         } else {
1903                             table_scriptFontsMotif[off++] = 0;
1904                         }
1905                         i++;
1906                     }
1907                 }
1908             } else {
1909                 table_scriptFontsMotif = EMPTY_SHORT_ARRAY;
1910             }
1911 
1912             //(11)short[] alphabeticSuffix
1913             head[INDEX_alphabeticSuffix] = (short)(head[INDEX_scriptFontsMotif] + table_scriptFontsMotif.length);
1914             table_alphabeticSuffix = new short[alphabeticSuffix.size() * 2];
1915             j = 0;
1916             for (Entry<Short, Short> entry : alphabeticSuffix.entrySet()) {
1917                 table_alphabeticSuffix[j++] = entry.getKey();
1918                 table_alphabeticSuffix[j++] = entry.getValue();
1919             }
1920 
1921             //(15)short[] fallbackScriptIDs; just put the ID in head
1922             head[INDEX_fallbackScripts] = getShortArrayID(fallbackScriptIDs);
1923 
1924             //(16)appendedfontpath
1925             head[INDEX_appendedfontpath] = getStringID(appendedfontpath);
1926 
1927             //(17)version
1928             head[INDEX_version] = getStringID(version);
1929 
1930             //(12)short[] StringIDs
1931             head[INDEX_stringIDs] = (short)(head[INDEX_alphabeticSuffix] + table_alphabeticSuffix.length);
1932             table_stringIDs = new short[stringIDNum + 1];
1933             System.arraycopy(stringIDs, 0, table_stringIDs, 0, stringIDNum + 1);
1934 
1935             //(13)StringTable
1936             head[INDEX_stringTable] = (short)(head[INDEX_stringIDs] + stringIDNum + 1);
1937             table_stringTable = stringTable.toString().toCharArray();
1938             //(14)
1939             head[INDEX_TABLEEND] = (short)(head[INDEX_stringTable] + stringTable.length());
1940 
1941             //StringTable cache
1942             stringCache = new String[table_stringIDs.length];
1943         }
1944 
1945         //////////////////////////////////////////////
1946         private HashMap<String, Short> scriptIDs;
1947         //elc -> Encoding.Language.Country
1948         private HashMap<String, Short> elcIDs;
1949         //componentFontNameID starts from "1", "0" reserves for "undefined"
1950         private HashMap<String, Short> componentFontNameIDs;
1951         private HashMap<String, Short> fontfileNameIDs;
1952         private HashMap<String, Integer> logicalFontIDs;
1953         private HashMap<String, Integer> fontStyleIDs;
1954 
1955         //componentFontNameID -> fontfileNameID
1956         private HashMap<Short, Short>  filenames;
1957 
1958         //elcID -> allfonts/logicalFont -> scriptID list
1959         //(1)if we have a "allfonts", then the length of the
1960         //   value array is "1", otherwise it's 5, each font
1961         //   must have their own individual entry.
1962         //scriptID list "short[]" is stored as an ID
1963         private HashMap<Short, short[]> sequences;
1964 
1965         //scriptID ->logicFontID/fontStyleID->componentFontNameID,
1966         //a 20-entry array (5-name x 4-style) for each script
1967         private HashMap<Short, Short[]> scriptFonts;
1968 
1969         //scriptID -> componentFontNameID
1970         private HashMap<Short, Short> scriptAllfonts;
1971 
1972         //scriptID -> exclusionRanges[]
1973         private HashMap<Short, int[]> exclusions;
1974 
1975         //scriptID -> fontpath
1976         private HashMap<Short, Short> awtfontpaths;
1977 
1978         //fontID -> fontID
1979         private HashMap<Short, Short> proportionals;
1980 
1981         //scriptID -> componentFontNameID
1982         private HashMap<Short, Short> scriptAllfontsMotif;
1983 
1984         //scriptID ->logicFontID/fontStyleID->componentFontNameID,
1985         private HashMap<Short, Short[]> scriptFontsMotif;
1986 
1987         //elcID -> stringID of alphabetic/XXXX
1988         private HashMap<Short, Short> alphabeticSuffix;
1989 
1990         private short[] fallbackScriptIDs;
1991         private String version;
1992         private String appendedfontpath;
1993 
1994         private void initLogicalNameStyle() {
1995             logicalFontIDs = new HashMap<String, Integer>();
1996             fontStyleIDs = new HashMap<String, Integer>();
1997             logicalFontIDs.put("serif",      0);
1998             logicalFontIDs.put("sansserif",  1);
1999             logicalFontIDs.put("monospaced", 2);
2000             logicalFontIDs.put("dialog",     3);
2001             logicalFontIDs.put("dialoginput",4);
2002             fontStyleIDs.put("plain",      0);
2003             fontStyleIDs.put("bold",       1);
2004             fontStyleIDs.put("italic",     2);
2005             fontStyleIDs.put("bolditalic", 3);
2006         }
2007 
2008         private void initHashMaps() {
2009             scriptIDs = new HashMap<String, Short>();
2010             elcIDs = new HashMap<String, Short>();
2011             componentFontNameIDs = new HashMap<String, Short>();
2012             /*Init these tables to allow componentFontNameID, fontfileNameIDs
2013               to start from "1".
2014             */
2015             componentFontNameIDs.put("", Short.valueOf((short)0));
2016 
2017             fontfileNameIDs = new HashMap<String, Short>();
2018             filenames = new HashMap<Short, Short>();
2019             sequences = new HashMap<Short, short[]>();
2020             scriptFonts = new HashMap<Short, Short[]>();
2021             scriptAllfonts = new HashMap<Short, Short>();
2022             exclusions = new HashMap<Short, int[]>();
2023             awtfontpaths = new HashMap<Short, Short>();
2024             proportionals = new HashMap<Short, Short>();
2025             scriptFontsMotif = new HashMap<Short, Short[]>();
2026             scriptAllfontsMotif = new HashMap<Short, Short>();
2027             alphabeticSuffix = new HashMap<Short, Short>();
2028             fallbackScriptIDs = EMPTY_SHORT_ARRAY;
2029             /*
2030               version
2031               appendedfontpath
2032             */
2033         }
2034 
2035         private int[] parseExclusions(String key, String exclusions) {
2036             if (exclusions == null) {
2037                 return EMPTY_INT_ARRAY;
2038             }
2039             // range format is xxxx-XXXX,yyyyyy-YYYYYY,.....
2040             int numExclusions = 1;
2041             int pos = 0;
2042             while ((pos = exclusions.indexOf(',', pos)) != -1) {
2043                 numExclusions++;
2044                 pos++;
2045             }
2046             int[] exclusionRanges = new int[numExclusions * 2];
2047             pos = 0;
2048             int newPos = 0;
2049             for (int j = 0; j < numExclusions * 2; ) {
2050                 String lower, upper;
2051                 int lo = 0, up = 0;
2052                 try {
2053                     newPos = exclusions.indexOf('-', pos);
2054                     lower = exclusions.substring(pos, newPos);
2055                     pos = newPos + 1;
2056                     newPos = exclusions.indexOf(',', pos);
2057                     if (newPos == -1) {
2058                         newPos = exclusions.length();
2059                     }
2060                     upper = exclusions.substring(pos, newPos);
2061                     pos = newPos + 1;
2062                     int lowerLength = lower.length();
2063                     int upperLength = upper.length();
2064                     if (lowerLength != 4 && lowerLength != 6
2065                         || upperLength != 4 && upperLength != 6) {
2066                         throw new Exception();
2067                     }
2068                     lo = Integer.parseInt(lower, 16);
2069                     up = Integer.parseInt(upper, 16);
2070                     if (lo > up) {
2071                         throw new Exception();
2072                     }
2073                 } catch (Exception e) {
2074                     if (FontUtilities.debugFonts() &&
2075                         logger != null) {
2076                         logger.config("Failed parsing " + key +
2077                                   " property of font configuration.");
2078 
2079                     }
2080                     return EMPTY_INT_ARRAY;
2081                 }
2082                 exclusionRanges[j++] = lo;
2083                 exclusionRanges[j++] = up;
2084             }
2085             return exclusionRanges;
2086         }
2087 
2088         private Short getID(HashMap<String, Short> map, String key) {
2089             Short ret = map.get(key);
2090             if ( ret == null) {
2091                 map.put(key, (short)map.size());
2092                 return map.get(key);
2093             }
2094             return ret;
2095         }
2096 
2097         class FontProperties extends Properties {
2098             public synchronized Object put(Object k, Object v) {
2099                 parseProperty((String)k, (String)v);
2100                 return null;
2101             }
2102         }
2103 
2104         private void parseProperty(String key, String value) {
2105             if (key.startsWith("filename.")) {
2106                 //the only special case is "MingLiu_HKSCS" which has "_" in its
2107                 //facename, we dont want to replace the "_" with " "
2108                 key = key.substring(9);
2109                 if (!"MingLiU_HKSCS".equals(key)) {
2110                     key = key.replace('_', ' ');
2111                 }
2112                 Short faceID = getID(componentFontNameIDs, key);
2113                 Short fileID = getID(fontfileNameIDs, value);
2114                 //System.out.println("faceID=" + faceID + "/" + key + " -> "
2115                 //    + "fileID=" + fileID + "/" + value);
2116                 filenames.put(faceID, fileID);
2117             } else if (key.startsWith("exclusion.")) {
2118                 key = key.substring(10);
2119                 exclusions.put(getID(scriptIDs,key), parseExclusions(key,value));
2120             } else if (key.startsWith("sequence.")) {
2121                 key = key.substring(9);
2122                 boolean hasDefault = false;
2123                 boolean has1252 = false;
2124 
2125                 //get the scriptID list
2126                 String[] ss = (String[])splitSequence(value).toArray(EMPTY_STRING_ARRAY);
2127                 short [] sa = new short[ss.length];
2128                 for (int i = 0; i < ss.length; i++) {
2129                     if ("alphabetic/default".equals(ss[i])) {
2130                         //System.out.println(key + " -> " + ss[i]);
2131                         ss[i] = "alphabetic";
2132                         hasDefault = true;
2133                     } else if ("alphabetic/1252".equals(ss[i])) {
2134                         //System.out.println(key + " -> " + ss[i]);
2135                         ss[i] = "alphabetic";
2136                         has1252 = true;
2137                     }
2138                     sa[i] = getID(scriptIDs, ss[i]).shortValue();
2139                     //System.out.println("scriptID=" + si[i] + "/" + ss[i]);
2140                 }
2141                 //convert the "short[] -> string -> stringID"
2142                 short scriptArrayID = getShortArrayID(sa);
2143                 Short elcID = null;
2144                 int dot = key.indexOf('.');
2145                 if (dot == -1) {
2146                     if ("fallback".equals(key)) {
2147                         fallbackScriptIDs = sa;
2148                         return;
2149                     }
2150                     if ("allfonts".equals(key)) {
2151                         elcID = getID(elcIDs, "NULL.NULL.NULL");
2152                     } else {
2153                         if (logger != null) {
2154                             logger.config("Error sequence def: <sequence." + key + ">");
2155                         }
2156                         return;
2157                     }
2158                 } else {
2159                     elcID = getID(elcIDs, key.substring(dot + 1));
2160                     //System.out.println("elcID=" + elcID + "/" + key.substring(dot + 1));
2161                     key = key.substring(0, dot);
2162                 }
2163                 short[] scriptArrayIDs = null;
2164                 if ("allfonts".equals(key)) {
2165                     scriptArrayIDs = new short[1];
2166                     scriptArrayIDs[0] = scriptArrayID;
2167                 } else {
2168                     scriptArrayIDs = sequences.get(elcID);
2169                     if (scriptArrayIDs == null) {
2170                        scriptArrayIDs = new short[5];
2171                     }
2172                     Integer fid = logicalFontIDs.get(key);
2173                     if (fid == null) {
2174                         if (logger != null) {
2175                             logger.config("Unrecognizable logicfont name " + key);
2176                         }
2177                         return;
2178                     }
2179                     //System.out.println("sequence." + key + "/" + id);
2180                     scriptArrayIDs[fid.intValue()] = scriptArrayID;
2181                 }
2182                 sequences.put(elcID, scriptArrayIDs);
2183                 if (hasDefault) {
2184                     alphabeticSuffix.put(elcID, getStringID("default"));
2185                 } else
2186                 if (has1252) {
2187                     alphabeticSuffix.put(elcID, getStringID("1252"));
2188                 }
2189             } else if (key.startsWith("allfonts.")) {
2190                 key = key.substring(9);
2191                 if (key.endsWith(".motif")) {
2192                     key = key.substring(0, key.length() - 6);
2193                     //System.out.println("motif: all." + key + "=" + value);
2194                     scriptAllfontsMotif.put(getID(scriptIDs,key), getID(componentFontNameIDs,value));
2195                 } else {
2196                     scriptAllfonts.put(getID(scriptIDs,key), getID(componentFontNameIDs,value));
2197                 }
2198             } else if (key.startsWith("awtfontpath.")) {
2199                 key = key.substring(12);
2200                 //System.out.println("scriptID=" + getID(scriptIDs, key) + "/" + key);
2201                 awtfontpaths.put(getID(scriptIDs, key), getStringID(value));
2202             } else if ("version".equals(key)) {
2203                 version = value;
2204             } else if ("appendedfontpath".equals(key)) {
2205                 appendedfontpath = value;
2206             } else if (key.startsWith("proportional.")) {
2207                 key = key.substring(13).replace('_', ' ');
2208                 //System.out.println(key + "=" + value);
2209                 proportionals.put(getID(componentFontNameIDs, key),
2210                                   getID(componentFontNameIDs, value));
2211             } else {
2212                 //"name.style.script(.motif)", we dont care anything else
2213                 int dot1, dot2;
2214                 boolean isMotif = false;
2215 
2216                 dot1 = key.indexOf('.');
2217                 if (dot1 == -1) {
2218                     if (logger != null) {
2219                         logger.config("Failed parsing " + key +
2220                                   " property of font configuration.");
2221 
2222                     }
2223                     return;
2224                 }
2225                 dot2 = key.indexOf('.', dot1 + 1);
2226                 if (dot2 == -1) {
2227                     if (logger != null) {
2228                         logger.config("Failed parsing " + key +
2229                                   " property of font configuration.");
2230 
2231                     }
2232                     return;
2233                 }
2234                 if (key.endsWith(".motif")) {
2235                     key = key.substring(0, key.length() - 6);
2236                     isMotif = true;
2237                     //System.out.println("motif: " + key + "=" + value);
2238                 }
2239                 Integer nameID = logicalFontIDs.get(key.substring(0, dot1));
2240                 Integer styleID = fontStyleIDs.get(key.substring(dot1+1, dot2));
2241                 Short scriptID = getID(scriptIDs, key.substring(dot2 + 1));
2242                 if (nameID == null || styleID == null) {
2243                     if (logger != null) {
2244                         logger.config("unrecognizable logicfont name/style at " + key);
2245                     }
2246                     return;
2247                 }
2248                 Short[] pnids;
2249                 if (isMotif) {
2250                     pnids = scriptFontsMotif.get(scriptID);
2251                 } else {
2252                     pnids = scriptFonts.get(scriptID);
2253                 }
2254                 if (pnids == null) {
2255                     pnids =  new Short[20];
2256                 }
2257                 pnids[nameID.intValue() * NUM_STYLES + styleID.intValue()]
2258                   = getID(componentFontNameIDs, value);
2259                 /*
2260                 System.out.println("key=" + key + "/<" + nameID + "><" + styleID
2261                                      + "><" + scriptID + ">=" + value
2262                                      + "/" + getID(componentFontNameIDs, value));
2263                 */
2264                 if (isMotif) {
2265                     scriptFontsMotif.put(scriptID, pnids);
2266                 } else {
2267                     scriptFonts.put(scriptID, pnids);
2268                 }
2269             }
2270         }
2271     }
2272 }